﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading;

using UT3Bots.UTItems;
using UT3Bots.Communications;

namespace UT3Bots
{
    public abstract class UTBot
    {
		private const int  GAME_PORT = 4530;
		private const int  VISUAL_PORT = 4531;

        private int _sleepTime = 500;

		private MessageHandler _utMessageHandler;
        private MessageHandler _vizMessageHandler;
		private BotCommands _botCommands;
		private BotEvents _botEvents;
		private GameState _gameState;
		private UTBotSelfState _selfState;
		private UTMap _map;
        
		private Thread _mainThread;

		private string _server;

		private string _botName;
		private BotMesh _botSkin;
		private BotColor _botColour;

		private bool _isRunningBot;
        private bool _isInGame;
		private UTIdentifier _id;

        TextWriterTraceListener tl = new TextWriterTraceListener(Console.Error);
		#region Properties
		/// <summary>
		/// Commands to control your bot
		/// </summary>
		public BotCommands Commands
		{
			get
			{
				return this._botCommands;
			}
		}

		/// <summary>
		/// Events that happen in the game, that you can react to
		/// </summary>
		public BotEvents Events
		{
			get
			{
				return this._botEvents;
			}
		}

		/// <summary>
		/// The state of the game as seen by your bot
		/// </summary>
		public GameState GameState
		{
			get
			{
				return this._gameState;
			}
		}

		/// <summary>
		/// The state of your bot itself
		/// </summary>
		public UTBotSelfState SelfState
		{
			get
			{
				return this._selfState;
			}
		}

		/// <summary>
		/// Information about the Map that the server is running
		/// </summary>
		public UTMap GameMap
		{
			get
			{
				return this._map;
			}
		}

		/// <summary>
		/// The name of your bot
		/// </summary>
		public string BotName
		{
			get
			{
				return _botName;
			}
		}

		/// <summary>
		/// The server you are connected to
		/// </summary>
		public string Server
		{
			get
			{
				return _server;
			}
		}

		/// <summary>
		/// Has the match begun and is your bot in it?
		/// </summary>
        public bool IsInGame
        {
            get
            {
				return _isInGame;
            }
        }

		/// <summary>
		/// Is the bot working?
		/// </summary>
		public bool IsWorking
		{
			get
			{
				return _isRunningBot;
			}
		}

        /// <summary>
        /// Time to wait between calls to ProcessActions
        /// </summary>
        public int SleepTime
        {
            get
            {
                return this._sleepTime;
            }
        }

        public int EnqueuedGameMessageCount
        {
            get
            {
                lock (_utMessageHandler.MessageQueue)
                {
                    return _utMessageHandler.MessageQueue.Count();
                }
            }
        }

        public int EnqueuedVisualizerMessageCount
        {
            get
            {
                return _vizMessageHandler.MessageQueue.Count();
            }
        }
		#endregion

        /// <summary>
        /// Set verbose to see server communications
        /// </summary>
        public bool VerboseOutput
        {
            get
            {
                return Trace.Listeners.Contains(tl);
            }
            set
            {
                if (value && !Trace.Listeners.Contains(tl))
                    Trace.Listeners.Add(tl);
                else
                {
                    Trace.Listeners.Remove(tl);
                }
            }
        }

        protected UTBot(string server, string botName, BotMesh botSkin, BotColor botColour) : this(server, botName, botSkin, botColour, 500) 
        {
        }
        
		protected UTBot(string server, string botName, BotMesh botSkin, BotColor botColour, int sleepTime)
        {
			//Set the bot settings
			this._server = server;
			this._botName = botName;
			this._botSkin = botSkin;
			this._botColour = botColour;

            this._sleepTime = sleepTime;

			this._isRunningBot = true;
			this._isInGame = false;

			//Add a trace listener
			//Trace.Listeners.Add(tl);

            this._botEvents = new BotEvents(this);
            this._gameState = new GameState();
            this._selfState = new UTBotSelfState(new Message(""));
            this._map = new UTMap();


			//Create the connection to the game server
			this._utMessageHandler = new MessageHandler(server, GAME_PORT);
            this._utMessageHandler.OnEventReceived += new EventHandler<MessageEventArgs>(GameDataReceived);
            this._utMessageHandler.OnErrorOccurred += new EventHandler<TcpErrorEventArgs>(GameErrorOccurred);
            

			//Create the connection to the visualizer server
            this._vizMessageHandler = new MessageHandler(server, VISUAL_PORT);
            this._vizMessageHandler.OnEventReceived += new EventHandler<MessageEventArgs>(VisualizerDataReceived);
            this._vizMessageHandler.OnErrorOccurred += new EventHandler<TcpErrorEventArgs>(VisualizerErrorOccurred);

			//Create the commands and events
            this._botCommands = new BotCommands(this, this._utMessageHandler.Connection);
			
			//Start the running thread
			this._mainThread = new Thread(new ThreadStart(Run));
            this._mainThread.Name = botName;
			this._mainThread.IsBackground = false;
			this._mainThread.Start();
		}

        /// <summary>
        /// Disconnect from server.
        /// </summary>
		public void Disconnect()
		{
			this._utMessageHandler.Disconnect();
			this._vizMessageHandler.Disconnect();
			this._isInGame = false;
			this._isRunningBot = false;
		}

		private void GameErrorOccurred(object sender, TcpErrorEventArgs e)
		{
			Trace.WriteLine("There was a problem contacting the server.", Global.TRACE_ERROR_CATEGORY);
			this.Disconnect();
		}

		private void VisualizerErrorOccurred(object sender, TcpErrorEventArgs e)
		{
			Trace.WriteLine("There was a problem contacting the visualizer server.", Global.TRACE_ERROR_CATEGORY);
			this.Disconnect();
		}


        internal void GameDataReceived(object sender, MessageEventArgs e)
		{
            ProcessEvent(e.Message);
        }


		internal void VisualizerDataReceived(object sender, MessageEventArgs e)
		{
            ProcessVizEvent(e.Message);
        }


		private void ProcessEvent(Message msg)
		{
			//Return if no valid message
			if (msg == null) return;

			//Do Command
			switch (msg.Event)
			{
				case EventMessage.STATE:
					if (msg.Info == InfoMessage.SELF_INFO)
					{
						this._selfState.UpdateState(msg);
					}
					this._gameState.UpdateGameState(msg);
					break;

				case EventMessage.INFO:
					Console.WriteLine(BotName + " connected to " + _server);
					this._utMessageHandler.Connection.SendLine(Message.BuildMessage(CommandMessage.INITIALIZE, this._botName, this._botSkin.ToString(), ((int)this._botColour).ToString()));
					break;

				case EventMessage.MATCH_ENDED:
					this._isInGame = false;
					this._botEvents.Trigger_OnMatchEnded(new MatchEndedEventArgs(
						new UTIdentifier(msg.Arguments[0]),
						msg.Arguments[1],
						msg.Arguments[2]
						));
					PerformGameOver();
					break;

				case EventMessage.WAITING_FOR_SPAWN:
					Trace.WriteLine("Match Not Started. Waiting For Spawn...", Global.TRACE_NORMAL_CATEGORY);
					break;

				case EventMessage.SPAWNED:
                    this._isInGame = true;
                    this._id = new UTIdentifier(msg.Arguments[1]);
					this._botEvents.Trigger_OnSpawned(new BotSpawnedEventArgs());
					break;

				case EventMessage.DIED:
					this._isInGame = false;
					this._botEvents.Trigger_OnDied(new HasDiedEventArgs(
						new UTIdentifier(msg.Arguments[0]),
						this._id
						));
					break;

				case EventMessage.KILLED:
					this._botEvents.Trigger_OnOtherBotDied(new HasDiedEventArgs(
						new UTIdentifier(msg.Arguments[0]),
						new UTIdentifier(msg.Arguments[1])
						));
					break;

				case EventMessage.SEEN_PLAYER:
					if (msg.Arguments.Length == 8)
					{
						bool isReachable = !string.IsNullOrEmpty(msg.Arguments[7]);
						this._botEvents.Trigger_OnSeenOtherBot(new SeenBotEventArgs(
							new UTIdentifier(msg.Arguments[0]),
							msg.Arguments[1],
							msg.Arguments[2],
							msg.Arguments[3],
							UTVector.Parse(msg.Arguments[4]),
							UTVector.Parse(msg.Arguments[5]),
							UTVector.Parse(msg.Arguments[6]),
							isReachable
							));
					}
					break;

				case EventMessage.BUMPED:
					if (msg.Arguments.Length == 3)
					{
						this._botEvents.Trigger_OnBumped(new BumpedEventArgs(
							new UTIdentifier(msg.Arguments[0]),
							UTVector.Parse(msg.Arguments[1]),
							UTVector.Parse(msg.Arguments[2])
							));
					}
					break;

				case EventMessage.HIT_WALL:
					if (msg.Arguments.Length == 3)
					{
						this._botEvents.Trigger_OnBumpedWall(new BumpedEventArgs(
							new UTIdentifier(msg.Arguments[0]),
							UTVector.Parse(msg.Arguments[1]),
							UTVector.Parse(msg.Arguments[2])
							));
					}
					break;

				case EventMessage.HEARD_NOISE:
					if (msg.Arguments.Length == 3)
					{
						this._botEvents.Trigger_OnHeardNoise(new HeardSoundEventArgs(
							new UTIdentifier(msg.Arguments[0]),
							UTVector.Parse(msg.Arguments[1]),
							float.Parse(msg.Arguments[2])
							));
					}
					break;

				case EventMessage.DAMAGED:
					if (msg.Arguments.Length == 5)
					{
						this._botEvents.Trigger_OnDamaged(new DamagedEventArgs(
							new UTIdentifier(msg.Arguments[0]),
							UTVector.Parse(msg.Arguments[1]),
							int.Parse(msg.Arguments[2]),
							msg.Arguments[3],
							UTVector.Parse(msg.Arguments[4])
							));
					}
					break;

				case EventMessage.CHAT:
					if (msg.Arguments.Length == 4)
					{
						this._botEvents.Trigger_OnReceivedChat(new ChatEventArgs(
							new UTIdentifier(msg.Arguments[0]),
							msg.Arguments[1],
							bool.Parse(msg.Arguments[2]),
							msg.Arguments[3]
							));
					}
					break;

				case EventMessage.FOUNDFALL:
					if (msg.Arguments.Length == 2)
					{
						this._botEvents.Trigger_OnFoundFall(new FallEventArgs(
							bool.Parse(msg.Arguments[0]),
							UTVector.Parse(msg.Arguments[1])
							));
					}
					break;

				case EventMessage.TAUNTED:
					if (msg.Arguments.Length == 2)
					{
						this._botEvents.Trigger_OnTaunted(new TauntedEventArgs(
							new UTIdentifier(msg.Arguments[0]),
							msg.Arguments[1]
							));
					}
					break;

				case EventMessage.WEAPON_CHANGED:
					if (msg.Arguments.Length == 2)
					{
						this._botEvents.Trigger_OnWeaponChanged(new WeaponChangedEventArgs(
							new UTIdentifier(msg.Arguments[0]),
							msg.Arguments[1]
							));
					}
					break;

				case EventMessage.GOT_PICKUP:
					if (msg.Arguments.Length == 3)
					{
						this._botEvents.Trigger_OnGotPickup(new PickupEventArgs(
							this._selfState.AddInventoryItem(msg),
							bool.Parse(msg.Arguments[2])
							));
					}
					break;

				case EventMessage.PATH:
                    Trace.Write(String.Format("Received get path response - {0}",String.Join(",",msg.Arguments)));
                    if (msg.Arguments.Length > 0 && (msg.Arguments.Length - 1) % 2 == 0)
                    {
                        Trace.WriteLine("Parsing response");
                        string id = msg.Arguments[0];

                        //Get the path nodes
                        List<UTNavPoint> nodes = new List<UTNavPoint>();
                        for (int i = 1; i < msg.Arguments.Length; i += 2)
                        {
                            nodes.Add(new UTNavPoint(
                                new UTIdentifier(msg.Arguments[i]),
                                UTVector.Parse(msg.Arguments[i + 1]),
                                true)
                                );
                        }

                        Trace.WriteLine("Firing path received event");
                        this._botEvents.Trigger_OnPathReceived(new PathEventArgs(
                            id,
                            nodes));
                    }
                    else
                    {
                        Trace.WriteLine("Invalid response.");
                    }
					break;

			}
		}


		private void ProcessVizEvent(Message msg)
		{
			//Return if no valid message
			if (msg == null) return;

			//Do Command
			switch (msg.Event)
			{
				case EventMessage.STATE:
				    this._map.UpdateState(msg);
			    break;
			}
		}


		private void PerformGameOver()
		{
			this._gameState.PrintScores(Console.Out);
			this.Disconnect();
		}


		private void Run()
		{
            if (_utMessageHandler.Connection.IsConnected)
                Console.WriteLine(BotName + " connected to game port");
            if (_vizMessageHandler.Connection.IsConnected)
                Console.WriteLine(BotName + " connected to visualizer port");

			while (this._isRunningBot)
			{
				if (this._isInGame)
				{
					ProcessActions();
				}
                Thread.Sleep(_sleepTime);
			}

            Console.WriteLine(BotName + " end of Run method.");
        }


		/// <summary>
		/// This method is where you should put main actions for your bot.
		/// It gets called about twice a second.
		/// </summary>
        protected virtual void ProcessActions()
        {
        }

    }

}
